#include "stdafx.h"
#include "libraries.h"

#include "Application.h"
#include "Texture2DManipulator.h"
#include "Filesystem.h"

using namespace Cvekas;

// Fast float to int conversion. Simple (int)n cast would flush processor pipelines - resulting in hundreds of wasted cycles,
// while this function takes only 3
static inline int fast_float2int(const float n)
{
	static int o;
	__asm
	{
		fld n
		lea eax, o
		fistp dword ptr[eax]
	}
	return o;
}

// Clever 1/sqrt(n) trick from quake3
static inline float fast_invsqrt(const float n)
{
	static int i;
	static float x2, y;
	const float threehalfs = 1.5f;
	x2 = n * 0.5f;
	y = n;
	i = * ( int * ) &y;  
	i = 0x5f3759df - ( i >> 1 );
	y = * ( float * ) &i;
	y = y * ( threehalfs - ( x2 * y * y ) );
	return y;
}

//Inverse distance between 2 2d vectors
float invdistance(const Vector2& v1, const Vector2& v2) 
{ 
	return fast_invsqrt((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); 
}

class Metaballs : public Application
{
public:
	Metaballs(HINSTANCE instance, const std::string& name, const std::string& log_filename)
		:
	Application(instance, name, log_filename)
	{
		Filesystem::init();
	}

	virtual ~Metaballs() {}

	virtual void initVideoDevice()
	{
		settings->insert("video", VideoSettings());

		(*settings)["video"]["ScreenWidth"] = window_width;
		(*settings)["video"]["ScreenHeight"] = window_height;

		window = WindowPtr(new Window(instance, name));
		window->showCursor(false);
		video_device->init(window);
	}

	virtual void init()
	{
		// Create dynamic texture for blobs
		blobs = video_device->getTextureManager()->createTexture2D("blobs", window_width/scale_factor, window_height/scale_factor, 
			D3DFMT_X8R8G8B8, false, true);
		// Create manipulator for blobs texture
		blobs_manipulator = Texture2DManipulatorPtr(new Texture2DManipulator(blobs));

		sprite_batch = video_device->createSpriteBatch(main_renderer, window_width, window_width, 1000);

		// Generate palette
		for(int n = 0; n < palette_size; ++n)
		{
			// Nice n^9 function scaled to fit in 0 - 255 range when input is in range 0 - pallete_size-1
			// And all this in pure integer arthmetic!
			int n3 = (n*n*n)/((palette_size*palette_size*palette_size)/64);
			char color = (char)((n3*n3*n3)/(64*16));
			palette[n] = Color(color, color, color, (char)255);
		}

		centers[0] = Vector2(0.5f, 0.5f);
		centers[1] = Vector2(0.33f, 0.85f);
		centers[2] = Vector2(0.45f, 0.28f);

		strengths[0] = 0.0781f;
		strengths[1] = 0.08906f;
		strengths[2] = 0.0986f;

		// Convert 0-1 coordinates to pixel coordinates
		for(int n = 0; n < n_centers; ++n)
		{
			centers[n].x *= (float)(window_width/scale_factor);
			centers[n].y *= (float)(window_height/scale_factor);
			strengths[n] *= (float)(window_height/scale_factor);
		}

		input->getMouse()->setVirtualResolution(window_width/scale_factor, window_height/scale_factor);
	}

	virtual bool update()
	{
		input->getMouse()->update();
		
		// Set first blob center to mouse coordinates
		MousePos pos = input->getMouse()->getAbsolutePos();
		centers[0] = Vector2((float)pos.x, (float)pos.y);
		
		// Update blobs texture
		for(int y = 0; y < (window_height / scale_factor); ++y)
		{
			for(int x = 0; x < (window_width / scale_factor); ++x)
			{
				// Calculate total field strength for every pixel...
				float total_strength = 0.0f;
				for(int n = 0; n < n_centers; ++n)
				{
					// Calculate inverse distance (1/distance) to blob center...
					float dist = invdistance(centers[n], Vector2((float)x, (float)y));
					// Scale distance by blob strenght, add to total strenght
					total_strength += strengths[n] * dist;
				}

				// Clamp it in range 0 - 1
				if(total_strength > 1.0f) total_strength = 1.0f;
				
				// Convert to address in our palette
				int index = fast_float2int(total_strength * (float)palette_size-1);
				// Store pixel color from palette
				blobs_manipulator->setPixel(x, y, palette[index]);
			}
		}
		blobs_manipulator->apply();

		return true;
	}

	virtual bool render()
	{
		video_device->beginScene();

		sprite_batch->draw(blobs, 0, RectF(), RectF(0.0f, 0.0f, float(window_width), float(window_height)));
		sprite_batch->present();

		main_renderer->render();

		video_device->endScene();
		main_renderer->endFrame();

		return true;
	}

private:
	static const int window_width = 512;
	static const int window_height = 512;
	static const int scale_factor = 2;

	static const int n_centers = 3;
	static const int palette_size = 512;

	Vector2 centers[n_centers];
	float strengths[n_centers];
	Color palette[palette_size];

	SpriteBatchPtr sprite_batch;
	
	Texture2DPtr blobs;
	Texture2DManipulatorPtr blobs_manipulator;

};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nShowCmd)
{
	Metaballs demo(hInstance, "Metaballs", "Metaballs.log");
	try
	{
		demo.run();
	}
	catch (std::exception& e)
	{
		MessageBox(NULL, e.what(), "Fatal error", NULL);
	}
	return 0;
}
